﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;

namespace Ooaavee.Events
{
    /// <summary>
    /// Provides data for the <see cref="Ooaavee.Events.EventWatcher.EventOccurred"/> event.
    /// </summary>
    [DebuggerDisplay("{DebuggerDisplay,nq}")]
    public sealed class EventWatcherEventArgs : EventArgs, IEvent
    {
        #region Private fields

        private readonly List<object> _args = new List<object>();
        private readonly EventInfo _underlyingEvent;

        #endregion

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is called from EventWatcher.MyEventCallback")]
        internal EventWatcherEventArgs(EventInfo underlyingEvent, IEnumerable<object> args)
        {
            _underlyingEvent = underlyingEvent;

            if (args != null)
            {
                _args.AddRange(args);
            }
        }

        /// <summary>
        /// Gets or sets a value which indicates if the <see cref="EventWatcher"/> should stop listening the 
        /// underlying event.
        /// </summary>
        public bool Unsubscribe { set; get; }

        #region IEvent Members

        /// <summary>
        /// Gets the name of the event.
        /// </summary>
        public string Name
        {
            get { return _underlyingEvent.Name; }
        }

        /// <summary>
        /// Gets the arguments of the event.
        /// </summary>
        public ReadOnlyCollection<object> Args
        {
            get { return _args.AsReadOnly(); }
        }

        /// <summary>
        /// Gets the underlying event.
        /// </summary>
        public EventInfo UnderlyingEvent
        {
            get { return _underlyingEvent; }
        }

        /// <summary>
        /// Tries to get the standard event parameters from this received event.
        /// </summary>
        /// <remarks>
        /// Event Design
        /// http://msdn.microsoft.com/en-us/library/ms229011.aspx
        /// </remarks>
        /// <typeparam name="TEventArgs">The type of event-specific data.</typeparam>
        /// <returns>the standard event parameters, or null not available</returns>
        public StandardEventParameters<TEventArgs> GetStandardEventParameters<TEventArgs>() where TEventArgs : EventArgs
        {
            // The method takes exactly two parameters.
            if (_args.Count != 2)
            {
                return null;
            }

            // The first parameter is named sender and is of type Object. This is the object that raised the event.
            object sender = _args[0];

            // The second parameter is named e and is of type EventArgs or a derived class of EventArgs. This is the event-specific data.
            TEventArgs e = _args[1] as TEventArgs;
            if (e == null)
            {
                return null;
            }

            StandardEventParameters<TEventArgs> standard = new StandardEventParameters<TEventArgs>
                                                               {
                                                                   Sender = sender,
                                                                   E = e
                                                               };
            return standard;
        }

        #endregion

        #region Debugger

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "See DebuggerDisplayAttribute marked on this class.")]
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string DebuggerDisplay
        {
            get 
            {
                StringBuilder s = new StringBuilder();

                s.Append(CsType(UnderlyingEvent.EventHandlerType.GetMethod("Invoke").ReturnType.Name));
                s.Append(" ");

                s.Append(UnderlyingEvent.Name);
                s.Append("(");

                // parameters
                ParameterInfo[] pi = UnderlyingEvent.EventHandlerType.GetMethod("Invoke").GetParameters();
                for (int i = 0; i < pi.Length; i++)
                {
                    s.Append(CsType(pi[i].ParameterType.ToString()));
                    s.Append(" ");
                    s.Append(pi[i].Name);
                    if (i < pi.Length - 1)
                    {
                        s.Append(", ");
                    }
                }
                s.Append(")");

                return s.ToString();
            }
        }

        /// <summary>
        /// Helper function to massage CLR type descriptions into C# aliases.
        /// </summary>
        /// <param name="clrType">Type name</param>
        /// <returns>C# alias, if appropriate</returns>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is called from the GetEventHandlerSignatureForDebugger() method")]
        private static string CsType(string clrType)
        {
            switch (clrType)
            {
                case "System.String":
                    return "string";
                case "System.SByte":
                    return "sbyte";
                case "System.Byte":
                    return "byte";
                case "System.Int16":
                    return "short";
                case "System.UInt16":
                    return "ushort";
                case "System.Int32":
                    return "int";
                case "System.UInt32":
                    return "uint";
                case "System.Int64":
                    return "long";
                case "System.UInt64":
                    return "ulong";
                case "System.Char":
                    return "char";
                case "System.Single":
                    return "float";
                case "System.Double":
                    return "double";
                case "System.Boolean":
                    return "bool";
                case "System.Decimal":
                    return "decimal";
                case "System.Void":
                    return "void";
                case "Void":
                    return "void";
                case "System.Object":
                    return "object";
                default:
                    return clrType;
            }
        }

        #endregion
    }
}